package org.larkdoc.util;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 基于：org.apache.tools.zip进行编写的zip压缩工具类
 * 
 * @author tmmk
 * @version [v1.0.1, 2016年12月9日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class ZipUtil {
    
    private static final Logger LOG = LoggerFactory.getLogger(ZipUtil.class);
    
    private static final String ENCODE_GBK = "GBK";
    
    private static final String WIN_FILE_SEPARATOR = "\\";
    
    private static final String UNIFIED_FILE_SEPARATOR = "/";
    
    /**
     * 创建ZIP文件
     * 
     * @param sourcePath
     *            文件或文件夹路径
     * @param zipPath
     *            生成的zip文件存在路径（包括文件名）
     */
    public static boolean zip(String sourcePath, String zipPath) {
        boolean result = true;
        FileOutputStream fos = null;
        ZipOutputStream zos = null;
        try {
            fos = new FileOutputStream(zipPath);
            zos = new ZipOutputStream(fos);
            result = zip(sourcePath, "", zos);
        } catch (FileNotFoundException e) {
            result = false;
            LOG.error("create zip {} file error. ", zipPath, e);
        } finally {
            IoUtil.close(zos, fos);
        }
        return result;
    }
    
    /**
     * 创建ZIP文件
     * 
     * @param sources
     * @param zipPath
     *            [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [违例类型] [违例说明]
     * @see [类、类#方法、类#成员]
     */
    public static boolean zip(List<String> sources, String zipPath) {
        boolean reuslt = true;
        if (null != sources && !sources.isEmpty() && StringUtils.isNotBlank(zipPath)) {
            FileOutputStream fos = null;
            ZipOutputStream zos = null;
            try {
                fos = new FileOutputStream(zipPath);
                zos = new ZipOutputStream(fos);
                for (String path : sources) {
                    reuslt = zip(path, zipPath, zos);
                    if (!reuslt) {
                        break;
                    }
                }
            } catch (FileNotFoundException e) {
                reuslt = false;
                LOG.error("create zip {} file error. ", zipPath, e);
            } finally {
                IoUtil.close(zos, fos);
            }
        } else {
            LOG.info("zip param is null. sources = {},zipPath = ", sources, zipPath);
        }
        return reuslt;
    }
    
    /**
     * 创建ZIP文件
     * 
     * @param sources
     * @param zipPath
     *            [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [违例类型] [违例说明]
     * @see [类、类#方法、类#成员]
     */
    public static boolean zip(String[] sources, String zipPath) {
        // Arrays.asList 此時返回的list不可add，remove，操作需要注意
        return zip(Arrays.asList(sources), zipPath);
    }
    
    public static void unZip(String zipFileName, String outDirectory) {
        if (StringUtils.isNotBlank(zipFileName) && StringUtils.isNotBlank(outDirectory)) {
            ZipFile zf = null;
            try {
                zf = new ZipFile(zipFileName, ENCODE_GBK);
            } catch (IOException e) {
                LOG.error("unZip is error. param = {}, exception : ", zipFileName, e);
            }
            Enumeration<?> enumeration = zf.getEntries();
            String fileName = null;
            while(enumeration.hasMoreElements()) {
                ZipEntry zipEntry = (ZipEntry)enumeration.nextElement();
                if (zipEntry.isDirectory()) {
                    fileName = zipEntry.getName();
                    fileName = fileName.substring(0, fileName.length() - 1);
                    createDir(outDirectory + File.separator + fileName);
                } else {
                    InputStream fis = null;
                    FileOutputStream out = null;
                    try {
                        // 解压文件从最深层次的文件开始解析，因此需要先反向创建文件夹
                        // 读取单个文件
                        fileName = zipEntry.getName();
                        // 统一处理文件路径,win系統下路徑不統一問題
                        fileName = fileName.replace(WIN_FILE_SEPARATOR, UNIFIED_FILE_SEPARATOR);
                        if (fileName.indexOf(UNIFIED_FILE_SEPARATOR) != -1) {
                            int lastIndexOf = fileName.lastIndexOf(UNIFIED_FILE_SEPARATOR);
                            createDir(outDirectory + File.separator + fileName.substring(0, lastIndexOf));
                        }
                        File f = new File(outDirectory + File.separator + fileName);
                        createDir(outDirectory);
                        fis = zf.getInputStream(zipEntry);
                        out = new FileOutputStream(f);
                        byte[] read = new byte[1024];
                        int c;
                        while((c = fis.read(read)) != -1) {
                            out.write(read, 0, c);
                        }
                    } catch (IOException e) {
                        LOG.error("unZip is exception :", e);
                    } finally {
                        IoUtil.close(out, fis);
                    }
                }
            }
        }
    }
    
    private static boolean zip(String sourcePath, String zipPath, ZipOutputStream zos) {
        return writeFileToZip(new File(sourcePath), "", zos);
    }
    
    private static boolean writeFileToZip(File file, String parentPath, ZipOutputStream zos) {
        boolean result = true;
        if (null != file && null != zos) {
            if (file.exists()) {
                if (file.isDirectory()) {
                    parentPath += file.getName() + File.separator;
                    File[] files = file.listFiles();
                    if (null != files && files.length > 0) {
                        for (File tempFile : files) {
                            writeFileToZip(tempFile, parentPath, zos);
                        }
                    }
                } else {
                    FileInputStream fis = null;
                    BufferedInputStream bis = null;
                    DataInputStream dis = null;
                    try {
                        fis = new FileInputStream(file);
                        bis = new BufferedInputStream(fis);
                        dis = new DataInputStream(bis);
                        ZipEntry ze = new ZipEntry(parentPath + file.getName());
                        zos.putNextEntry(ze);
                        // ZipOutputStream 采用apache的ant.jar包 解決中文乱码问题。
                        zos.setEncoding(ENCODE_GBK);
                        byte[] content = new byte[1024];
                        int len;
                        while((len = fis.read(content)) != -1) {
                            zos.write(content, 0, len);
                            zos.flush();
                        }
                    } catch (FileNotFoundException e) {
                        result = false;
                        LOG.error("create zip file error. ", e);
                    } catch (IOException e) {
                        result = false;
                        LOG.error("create zip file error. ", e);
                    } finally {
                        IoUtil.close(dis, bis, fis);
                    }
                }
            } else {
                LOG.info("writeFileToZip File is not find . File ={}", file);
            }
        } else {
            LOG.info("writeFileToZip param is null. File ={}, ZipOutputStream ={}", file, zos);
        }
        return result;
    }
    
    private static boolean createDir(String directory) {
        boolean result = true;
        if (StringUtils.isNoneBlank(directory)) {
            // 替换路径符号：统一路径符号为：/
            directory = directory.replace(WIN_FILE_SEPARATOR, UNIFIED_FILE_SEPARATOR);
            String[] dirArr = directory.split(UNIFIED_FILE_SEPARATOR);
            String pathTemp = "";
            File file = null;
            // 解决多层路径问题。
            for (String dir : dirArr) {
                if (StringUtils.isBlank(dir)) {
                    continue;
                }
                pathTemp += StringUtils.isNotBlank(pathTemp) ? File.separator + dir : dir;
                file = new File(pathTemp);
                if (null != file && file.exists() && file.isDirectory()) {
                    LOG.debug("Directory is exists : {}", pathTemp);
                    continue;
                }
                result = file.mkdir();
            }
        }
        return result;
    }
    
    public static void main(String[] args) {
        // 测试把F盘下的所有文件打包压缩成sql.zip文件放在F盘根目录下
        // ZipUtil.zip(new String[] {"D:/logs/测试压缩", "D:/logs/20161208100000025802.png"}, "D:/logs/压缩测试.zip");
        // ZipUtil.zip("D:/logs/20161208100000025802.png", "D:/logs/单文件测试.zip");
        ZipUtil.unZip("D://logs/喂喂喂.zip", "D:/logs/test");
    }
}
